Skip to content

Vue2 vs Vue3 响应式原理深入对比

一、核心要点速览

💡 核心考点

  • Vue 2: 基于 Object.defineProperty 的数据劫持,配合 DepWatcher 实现。
  • Vue 3: 基于 Proxy 的代理机制,直接监听对象而非属性。
  • 性能优化: Vue 3 引入了“懒代理”机制,极大地提升了初始化性能。
  • 功能突破: Vue 3 完美支持了对象新增属性、删除属性以及数组索引监听。

二、Vue 2 响应式原理:数据劫持

Vue 2 在初始化时,会调用 Object.defineProperty 递归遍历 data 中的所有属性,将它们转为 gettersetter

1. 工作流程图

Vue 2 响应式流程图

  • Getter (依赖收集):当组件渲染或计算属性读取该属性时,触发 getter,将当前的 Watcher 添加到该属性的 Dep(订阅器)中。
  • Setter (派发更新):当修改属性值时,触发 setter,调用 dep.notify() 通知所有订阅了该属性的 Watcher 进行更新。

2. 核心局限性

  • 对象限制:无法检测到对象属性的新增或删除(必须使用 Vue.set / this.$set)。
  • 数组限制:无法监听通过索引修改数组项(如 arr[0] = 1)或直接修改 length 属性的操作。
  • 深度遍历:初始化时必须一次性递归到底,对于深层嵌套的大对象,性能开销巨大。

3. 数组的特殊处理 (黑科技)

由于 Object.defineProperty 无法监听数组内部变化,Vue 2 通过重写数组原型方法来实现响应式:

  • 重写的 7 个方法push | pop | shift | unshift | splice | sort | reverse
  • 原理:在调用这些方法时,Vue 会手动触发 dep.notify() 通知更新。

三、Vue 3 响应式原理:全量代理

Vue 3 使用 ES6 的 Proxy 对象重构了整个系统。Proxy 可以拦截对象的所有操作,而不仅仅是某个属性的读写。

1. 工作流程图

Vue 3 Proxy 拦截流程图

  • 拦截全面:除了 getset,还能拦截 deletePropertyhasownKeys 等多达 13 种操作。
  • 原生数组支持Proxy 能够原生拦截数组的索引赋值(arr[0] = 1)和 length 变化。
  • 按需代理 (Lazy):Vue 3 不再在初始化时递归遍历。只有当程序访问到深层对象时,才会对其创建新的 Proxy

2. 代码简化示例

javascript
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      track(target, key); // 收集依赖
      const res = Reflect.get(target, key, receiver);
      // 如果是对象,则递归代理 (Lazy 处理)
      return isObject(res) ? reactive(res) : res;
    },
    set(target, key, value, receiver) {
      const res = Reflect.set(target, key, value, receiver);
      trigger(target, key); // 触发更新
      return res;
    }
  });
}

四、性能对比:O(n) vs O(1)

Vue 3 在初始化阶段的性能优势主要来源于按需代理

初始化性能对比图

  • Vue 2:初始化成本与对象属性总数 $n$ 成正比($O(n)$)。属性越多,页面加载越慢。
  • Vue 3:初始化仅需代理根层级,成本恒定($O(1)$)。深层嵌套属性的转换被推迟到了访问时。

五、核心差异汇总表

特性Vue 2 (defineProperty)Vue 3 (Proxy)
监听粒度属性级别(需预先定义)对象级别(动态拦截)
新增/删除属性❌ 不支持(需 $set原生支持
数组索引/长度❌ 不支持(需重写原型)原生支持
初始化性能一般(需全量递归)极佳(按需代理)
Map/Set 支持❌ 弱原生支持
浏览器兼容性IE 9+不支持 IE(Proxy 无法 polyfill)

六、面试回答要点总结

Q: 请简述 Vue 2 与 Vue 3 响应式原理的区别?

  1. 核心 API:Vue 2 使用 Object.defineProperty,Vue 3 使用 Proxy
  2. 实现机制
    • Vue 2 通过递归属性实现劫持,存在无法检测新增属性和数组索引修改的缺陷。
    • Vue 3 代理整个对象,拦截操作更全面,且能完美支持数组和集合类型。
  3. 性能差异
    • Vue 2 在初始化时一次性递归,大对象性能差。
    • Vue 3 采用懒代理(访问时递归),初始化极快,内存占用更低。
  4. 兼容性:Vue 3 放弃了对 IE 的支持,以换取更强大和高效的底层实现。

七、进阶面试避坑:Object.defineProperty 真的不能拦截数组吗?

⚠️ 面试陷阱题

面试官问:很多人说 Vue 2 不支持数组响应式是因为 Object.defineProperty 无法拦截数组,这话对吗?

标准回答: 这句话是不准确的。

  1. 技术可行性:从技术上讲,Object.defineProperty 完全可以拦截数组下标(如 arr[0]),因为数组在 JS 中本质上也是对象,索引就是属性名。
  2. Vue 2 为何不用:尤雨溪曾在 GitHub 回复过,不采用的主要原因是性能代价太大。如果数组有 10 万条数据,初始化时就要定义 10 万个 getter/setter,这对性能是毁灭性的打击。
  3. 折中方案:所以 Vue 2 选择了“重写数组原型方法”这一折中方案,而放弃了对数组下标的监听。

八、总结一句话

  • Vue 2: defineProperty + 递归遍历 + 数组原型重写 = 有局限的响应式 ⚠️
  • Vue 3: Proxy + 懒加载 + 完整拦截 = 更完美的响应式系统
最近更新